/*
 * Copyright 2021 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SurfaceFillContext_DEFINED
#define SurfaceFillContext_DEFINED

#include "src/gpu/ganesh/SurfaceContext.h"

#include "src/gpu/ganesh/ops/OpsTask.h"

namespace skgpu::ganesh {
class SurfaceFillContext : public SurfaceContext {
public:
    SurfaceFillContext(GrRecordingContext *rContext, GrSurfaceProxyView readView, GrSurfaceProxyView writeView,
        const GrColorInfo &colorInfo);

    SurfaceFillContext *asFillContext() override
    {
        return this;
    }

    OpsTask *getOpsTask();

#if defined(GR_TEST_UTILS)
    OpsTask *testingOnly_PeekLastOpsTask()
    {
        return fOpsTask.get();
    }
#endif

    /* *
     * Provides a performance hint that the render target's contents are allowed
     * to become undefined.
     */
    void discard();

    void resolveMSAA();

    /* *
     * Clear the rect of the render target to the given color.
     * @param rect  the rect to clear to
     * @param color the color to clear to.
     */
    template <SkAlphaType AlphaType> void clear(const SkIRect &rect, const SkRGBA4f<AlphaType> &color)
    {
        this->internalClear(&rect, this->adjustColorAlphaType(color));
    }

    /* * Clears the entire render target to the color. */
    template <SkAlphaType AlphaType> void clear(const SkRGBA4f<AlphaType> &color)
    {
        this->internalClear(nullptr, this->adjustColorAlphaType(color));
    }

    /* *
     * Clear at minimum the pixels within 'scissor', but is allowed to clear the full render target
     * if that is the more performant option.
     */
    template <SkAlphaType AlphaType> void clearAtLeast(const SkIRect &scissor, const SkRGBA4f<AlphaType> &color)
    {
        this->internalClear(&scissor, this->adjustColorAlphaType(color),
            /* upgrade to full */ true);
    }

    /* * Fills 'dstRect' with 'fp' */
    void fillRectWithFP(const SkIRect &dstRect, std::unique_ptr<GrFragmentProcessor>);

    /* *
     * A convenience version of fillRectWithFP that applies a coordinate transformation via
     * GrMatrixEffect.
     */
    void fillRectWithFP(const SkIRect &dstRect, const SkMatrix &localMatrix, std::unique_ptr<GrFragmentProcessor>);

    /* * Fills 'dstRect' with 'fp' using a local matrix that maps 'srcRect' to 'dstRect' */
    void fillRectToRectWithFP(const SkRect &srcRect, const SkIRect &dstRect, std::unique_ptr<GrFragmentProcessor> fp)
    {
        SkMatrix lm = SkMatrix::RectToRect(SkRect::Make(dstRect), srcRect);
        this->fillRectWithFP(dstRect, lm, std::move(fp));
    }

    /* * Fills 'dstRect' with 'fp' using a local matrix that maps 'srcRect' to 'dstRect' */
    void fillRectToRectWithFP(const SkIRect &srcRect, const SkIRect &dstRect, std::unique_ptr<GrFragmentProcessor> fp)
    {
        this->fillRectToRectWithFP(SkRect::Make(srcRect), dstRect, std::move(fp));
    }

    /* * Fills the entire render target with the passed FP. */
    void fillWithFP(std::unique_ptr<GrFragmentProcessor> fp)
    {
        this->fillRectWithFP(SkIRect::MakeSize(fWriteView.proxy()->dimensions()), std::move(fp));
    }

    /* *
     * Draws the src texture with no matrix. The dstRect is the dstPoint with the width and height
     * of the srcRect. The srcRect and dstRect are clipped to the bounds of the src and dst surfaces
     * respectively.
     */
    bool blitTexture(GrSurfaceProxyView, const SkIRect &srcRect, const SkIPoint &dstPoint);

    sk_sp<GrRenderTask> refRenderTask();

    int numSamples() const
    {
        return this->asRenderTargetProxy()->numSamples();
    }
    bool wrapsVkSecondaryCB() const
    {
        return this->asRenderTargetProxy()->wrapsVkSecondaryCB();
    }

    SkArenaAlloc *arenaAlloc()
    {
        return this->arenas()->arenaAlloc();
    }
    sktext::gpu::SubRunAllocator *subRunAlloc()
    {
        return this->arenas()->subRunAlloc();
    }

    const GrSurfaceProxyView &writeSurfaceView() const
    {
        return fWriteView;
    }

protected:
    OpsTask *replaceOpsTask();

    /* *
     * Creates a constant color paint for a clear, using src-over if possible to improve batching.
     */
    static void ClearToGrPaint(std::array<float, 4> color, GrPaint *paint);

    void addOp(GrOp::Owner);

    template <SkAlphaType AlphaType> static std::array<float, 4> ConvertColor(SkRGBA4f<AlphaType> color);

    template <SkAlphaType AlphaType> std::array<float, 4> adjustColorAlphaType(SkRGBA4f<AlphaType> color) const;

    GrSurfaceProxyView fWriteView;

private:
    sk_sp<GrArenas> arenas()
    {
        return fWriteView.proxy()->asRenderTargetProxy()->arenas();
    }

    void addDrawOp(GrOp::Owner);

    /* * Override to be notified in subclass before the current ops task is replaced. */
    virtual void willReplaceOpsTask(OpsTask *prevTask, OpsTask *nextTask) {}

    /* *
     * Override to be called to participate in the decision to discard all previous ops if a
     * fullscreen clear occurs.
     */
    virtual OpsTask::CanDiscardPreviousOps canDiscardPreviousOpsOnFullClear() const
    {
        return OpsTask::CanDiscardPreviousOps::kYes;
    }

    void internalClear(const SkIRect *scissor, std::array<float, 4> color, bool upgradePartialToFull = false);

    SkDEBUGCODE(void onValidate() const override;)

        // The OpsTask can be closed by some other surface context that has picked it up. For this
        // reason, the OpsTask should only ever be accessed via 'getOpsTask'.
        sk_sp<OpsTask> fOpsTask;

    using INHERITED = SurfaceContext;
};

template <> inline std::array<float, 4> SurfaceFillContext::ConvertColor<kPremul_SkAlphaType>(SkPMColor4f color)
{
    return color.unpremul().array();
}

template <> inline std::array<float, 4> SurfaceFillContext::ConvertColor<kUnpremul_SkAlphaType>(SkColor4f color)
{
    return color.premul().array();
}

template <SkAlphaType AlphaType>
std::array<float, 4> SurfaceFillContext::adjustColorAlphaType(SkRGBA4f<AlphaType> color) const
{
    if (AlphaType == kUnknown_SkAlphaType || this->colorInfo().alphaType() == kUnknown_SkAlphaType) {
        return color.array();
    }
    return (AlphaType == this->colorInfo().alphaType()) ? color.array() : ConvertColor(color);
}
} // namespace skgpu::ganesh

#endif // SurfaceFillContext_DEFINED
